/*
 * mem_pool.c
 *
 *  Created on: 2016年9月7日
 *      Author: churchillyik
 */
#include "mem_pool.h"

lpmempool_t mempool_init(uint32_t nb_blocks, uint32_t max_nb_blocks, uint32_t block_bytes) {
	lpmempool_t mempool;
	lpmemnode_t node = NULL;

	mempool = (lpmempool_t) malloc(sizeof(mempool_t));
	if (mempool == NULL) {
		fprintf(stderr, ">>>>> ERROR: Cannot alloc mempool_t!!!\n");
		exit(EXIT_FAILURE);
	}

	memset(mempool, 0, sizeof(mempool_t));
	mempool->max_nb_blocks = max_nb_blocks;
	mempool->block_bytes = (block_bytes == 0? DEFAULT_BLOCK_SIZE : block_bytes);

	uint32_t block_size = sizeof(memnode_t) + mempool->block_bytes;
	mempool->nb_blocks_per_cache = (MEMPOOL_CACHE_LINE_SIZE - sizeof(void *)) / block_size;
	mempool->sz_blocks_per_cache = sizeof(void *) + mempool->nb_blocks_per_cache * block_size;

	pthread_spin_init(&mempool->lock, PTHREAD_PROCESS_PRIVATE);

	if (nb_blocks > 0) {

		uint32_t caches_size = sizeof(void *) + nb_blocks * block_size;
		void *blocks = (void *) malloc(caches_size);
		if (blocks == NULL) {
			fprintf(stderr, ">>>>> ERROR: Cannot alloc init blocks(caches_size = %d)!!!\n", caches_size);
			exit(EXIT_FAILURE);
		}
		mempool->memory_cost += caches_size;

		mempool->nb_in_queue = mempool->nb_total = nb_blocks;

		uint32_t i;
		for (i = 0; i < nb_blocks; i++) {
			node = (lpmemnode_t)(blocks + sizeof(void *) + i * block_size);
			if (mempool->head == NULL) {
				mempool->head = mempool->tail = node;
			} else {
				mempool->tail->next = node;
				mempool->tail = node;
			}
		}

		if (mempool->tail != NULL) {
			mempool->tail->next = NULL;
		}

		mempool->cache_list = blocks;
		MP_NEXT_CACHE(mempool->cache_list) = NULL;
	}

	mempool_print(mempool);
	return mempool;
}

lpmemnode_t mempool_alloc(lpmempool_t mempool) {
	if (mempool == NULL) {
		return NULL;
	}

	lpmemnode_t node = NULL;

	pthread_spin_lock(&mempool->lock);
	if (mempool->head != NULL) {
		node = mempool->head;
		mempool->head = node->next;

		if (node == mempool->tail) {
			mempool->head = mempool->tail = NULL;
		}

		++mempool->nb_in_use;
		--mempool->nb_in_queue;
		//printf("mempool nb_in_use = %d, nb_in_queue = %d\n", mempool->nb_in_use,
		//		mempool->nb_in_queue);
		pthread_spin_unlock(&mempool->lock);
	} else {
		if (mempool->max_nb_blocks > 0 && mempool->nb_total >= mempool->max_nb_blocks) {
			pthread_spin_unlock(&mempool->lock);
			return NULL;
		}
		pthread_spin_unlock(&mempool->lock);

		if (!mempool->refuse_alloc) {

			void *blocks = (void *) malloc(MEMPOOL_CACHE_LINE_SIZE);
			if (blocks == NULL) {
				mempool->refuse_alloc = 1;
				return NULL;
			} else {
				node = (lpmemnode_t)(blocks + sizeof(void *));

				pthread_spin_lock(&mempool->lock);
				MP_NEXT_CACHE(blocks) = mempool->cache_list;
				mempool->cache_list = blocks;
				mempool->nb_in_use += mempool->nb_blocks_per_cache;
				mempool->nb_total += mempool->nb_blocks_per_cache;
				mempool->memory_cost += MEMPOOL_CACHE_LINE_SIZE;
				pthread_spin_unlock(&mempool->lock);

				uint32_t i;
				uint32_t block_size = sizeof(memnode_t) + mempool->block_bytes;
				lpmemnode_t new_node = NULL;
				for (i = 1; i < mempool->nb_blocks_per_cache; i++) {
					new_node = (lpmemnode_t)(blocks + sizeof(void *) + i * block_size);
					mempool_free(mempool, new_node);
				}
			}
		} else {
			mempool->wait_for_alloc_cnts++;
			if (mempool->wait_for_alloc_cnts >= MAX_WAIT_FOR_ALLOC_COUNTS) {
				mempool->wait_for_alloc_cnts = 0;
				mempool->refuse_alloc = 0;
			}

			return NULL;
		}
	}
	
	return node;
}

int mempool_free(lpmempool_t mempool, lpmemnode_t node) {
	if (mempool == NULL || node == NULL) {
		// 如果跑到这里，很可能就已经发生内存泄露
		// printf("mempool = %p | node = %p | lcore_id = %d\n", mempool, node, node->lcore_id);
		return 0;
	}

	pthread_spin_lock(&mempool->lock);
	if (mempool->head == NULL) {
		mempool->head = mempool->tail = node;
	} else {
		node->next = mempool->head;
		mempool->head = node;
	}

	++mempool->nb_in_queue;
	--mempool->nb_in_use;

	//printf("mempool free nb_in_queue = %d, nb_in_use = %d\n",
	//		mempool->nb_in_queue, mempool->nb_in_use);

	pthread_spin_unlock(&mempool->lock);
	return 1;
}

void mempool_destroy(lpmempool_t *pmempool) {
	lpmempool_t mempool = *pmempool;
	if (mempool == NULL) {
		return;
	}

	mempool_print(mempool);

	void *cache = mempool->cache_list, *next;
	while (cache != NULL) {
		next = MP_NEXT_CACHE(cache);
		free(cache);
		cache = next;
	}

	pthread_spin_destroy(&mempool->lock);

	free(mempool);
	*pmempool = NULL;
}

void mempool_print(lpmempool_t mempool) {
	printf("##### mempool status #####\n");
	printf("\t* max_nb_blocks = %d\n", mempool->max_nb_blocks);
	printf("\t* block_bytes = %d\n", mempool->block_bytes);
	printf("\t* nb_total = %d, nb_in_use = %d, nb_in_queue = %d\n"
		, mempool->nb_total, mempool->nb_in_use, mempool->nb_in_queue);
	printf("\t* memory_cost(%.4f MBytes) = blocks_size(%.4f MBytes) + waste(%.4f Mbytes)\n"
		, (double)mempool->memory_cost / 1024 / 1024
		, (double)mempool->nb_total * (sizeof(memnode_t) + mempool->block_bytes) / 1024 / 1024
		, (double)(mempool->memory_cost - mempool->nb_total * (sizeof(memnode_t) + mempool->block_bytes)) / 1024 / 1024
	);
}